Skip to content

perf: hoist v3/v4/v5 schema migrations to load-time - #198

Merged
EtienneLescot merged 2 commits into
release/v1.8.0from
ponytail/hoist-schema-migrations
Jul 29, 2026
Merged

perf: hoist v3/v4/v5 schema migrations to load-time#198
EtienneLescot merged 2 commits into
release/v1.8.0from
ponytail/hoist-schema-migrations

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

perf: hoist v3/v4/v5 schema migrations to load-time

Removes the per-parse schema migration overhead from every documentSchema.parse(...) call.

What changed

The pre-hoist documentSchema was wrapped in a z.preprocess that ran upgradeV3DocumentToV4 + upgradeV4DocumentToV5 on every parse — including in-memory parses of documents that were already v5. Both upgraders are guarded by if (doc.schemaVersion !== 3|4) return raw;, so they were fast no-ops for v5 inputs but still a function call per parse. Multiplied across every setDocument / saveDocument / loadProject round-trip, that's a measurable per-parse overhead.

The fix hoists the migration to load time:

  1. documentSchema is now a pure v5 validator — the z.preprocess wrapper is removed and the upgraders (upgradeV3DocumentToV4, upgradeV4DocumentToV5) are now exported.
  2. New migrateRawDocumentToCurrent helper in src/lib/ai-edition/document/migrate.ts — composes the two upgraders into the load-time equivalent of the old chain. Modeled after the existing migrateProjectDataToAxcutDocument (v2 → v3).
  3. Two load sites wired to run the helper before documentSchema.parse:
    • electron/ai-edition/document-service.tsgetProject and listProjects JSON-read paths.
    • src/native/browserShim.tsget callback and the localStorage-load IIFE (the shim persists documents to localStorage in browser-mode preview, and new shim documents are now written as v5 directly so the renderer's pure-v5 parseDocument accepts them on the first read).
  4. Two renderer disk-load paths wired to run the helper before documentSchema.parse:
    • src/components/ai-edition/NewEditorShell.tsxhandleBrowseProject (loads a .openscreen file via window.electronAPI.loadProjectFile).
    • src/components/ai-edition/EditorEmptyState.tsxopenLoadedProject (same path).
  5. migrateProjectDataToAxcutDocument updated to call the new helper before the v5-validating documentSchema.parse, so the v2 → v3 → v4 → v5 chain still lives in one place.
  6. Existing schema tests updated to call the helper before parsing v3/v4 inputs (modeling the new load-time contract).
  7. Test fixtures updatedprojectStore.test.ts, useTimeline.test.ts, and EditorEmptyState.test.tsx use a v3 sampleDoc to model the bridge response. After the hoist the bridge contract is v5 (every load site runs the helper), so the fixtures now use schemaVersion: 5.
  8. New tests for migrateRawDocumentToCurrent in migrate.test.ts:
    • v3 doc → v5
    • v4 doc → v5
    • v5 doc → v5 (no-op)
    • non-document input (null/undefined/primitives/arrays/v2) → unchanged
    • Already-v5 input round-trips through documentSchema.parse with no error
  9. Technical documentation updated — document-model.md describes the load-time chain and the new contract.

Why this matters

Every render-side setDocument / saveDocument / loadProject is now a single z.literal(5) + shape check on already-v5 data, instead of a function call into each of the two upgraders + the parse. The upgraders only run on the first read of a v3/v4 document from disk (or localStorage), which is the only time the work is needed.

Files changed

 electron/ai-edition/document-service.ts                    |  15 +-
 src/components/ai-edition/EditorEmptyState.test.tsx        |   6 +-
 src/components/ai-edition/EditorEmptyState.tsx             |   7 +-
 src/components/ai-edition/NewEditorShell.tsx               |   7 +-
 src/lib/ai-edition/document/migrate.test.ts                | 138 +++++++++-
 src/lib/ai-edition/document/migrate.ts                     |  33 ++-
 src/lib/ai-edition/schema/index.test.ts                    | 139 ++++++----
 src/lib/ai-edition/schema/index.ts                         |  36 ++-
 src/lib/ai-edition/store/projectStore.test.ts              |   6 +-
 src/lib/ai-edition/store/useTimeline.test.ts               |   6 +-
 src/native/browserShim.ts                                  |  24 ++-
 technical-documentation/architecture/document-model.md    |  18 +-

Gates

  • npx tsc --noEmit — clean (exit 0)
  • npm run test — 1150/1150 (1144 baseline + 6 new tests for migrateRawDocumentToCurrent)
  • npm run lint — no new warnings (7 pre-existing warnings, all in files unrelated to this PR)

Interaction with #195

PR #195 (in flight on ponytail/drop-legacy-aspect-and-annotation) adds a v5→v6 upgrader to the same z.preprocess chain. This PR removes that chain, so #195's upgrader needs to be hoisted to load-time too. Two options:

Either works. The conflict is mechanical, not architectural.

Labels

  • Performance
  • Minor
  • Not platform-specific

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c2c5efb-7b77-4084-a6ed-44091b24dc1f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ponytail/hoist-schema-migrations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Removes the per-parse schema migration overhead from every documentSchema.parse(...)
call. The pre-hoist schema was wrapped in a z.preprocess that ran upgradeV3DocumentToV4
+ upgradeV4DocumentToV5 on every parse, including in-memory parses of v5 docs.

- documentSchema is now a pure v5 validator; the z.preprocess wrapper is removed
  and both upgraders are exported
- New migrateRawDocumentToCurrent helper in document/migrate.ts composes the
  two upgraders into the load-time equivalent of the old chain
- DocumentService.getProject / listProjects wired to run the helper before parse
- browserShim get callback and localStorage-load IIFE wired; new shim docs
  written as v5 directly
- NewEditorShell.handleBrowseProject and EditorEmptyState.openLoadedProject
  (renderer disk-load paths) wired to run the helper before parse
- migrateProjectDataToAxcutDocument updated to call the new helper so the
  v2 -> v3 -> v4 -> v5 chain still lives in one place
- Existing schema tests updated to model the new contract
- New tests for migrateRawDocumentToCurrent (v3/v4/v5/non-doc/v2)
- Test fixtures (projectStore, useTimeline, EditorEmptyState) bumped to v5
  to model the new bridge contract (load sites now return v5)
- technical-documentation/architecture/document-model.md updated

Every render-side setDocument / saveDocument / loadProject is now a single
z.literal(5) + shape check on already-v5 data, instead of a function call
into each upgrader + the parse.

Note for #195: that PR adds a v5->v6 upgrader to the same z.preprocess chain
this PR removes. Either land this PR first and rebase #195 on top, or combine
them — the only conflict point is the z.preprocess line.
@EtienneLescot
EtienneLescot force-pushed the ponytail/hoist-schema-migrations branch from 0b630d3 to 2239d35 Compare July 29, 2026 09:16
The Electron main bundle is built by vite-plugin-electron with configFile:
false, so the root resolve.alias never applies to it. Importing the composer
from document/migrate.ts dragged that module's @/-aliased value import
(PROJECT_VERSION) into main and broke the build.

Move migrateRawDocumentToCurrent into schema/index.ts (alias-free), re-export
it from migrate.ts for existing callers, and stop hardcoding schemaVersion in
the browser shim so it tracks axcutSchemaVersion across bumps.
@EtienneLescot
EtienneLescot force-pushed the ponytail/hoist-schema-migrations branch from 2239d35 to cf9566a Compare July 29, 2026 09:22
@EtienneLescot
EtienneLescot merged commit e95ba8e into release/v1.8.0 Jul 29, 2026
9 checks passed
@EtienneLescot
EtienneLescot deleted the ponytail/hoist-schema-migrations branch July 29, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants